﻿//
// File: DirTraversal01.cs
//
// This sample program shows different ways to traverse a directory structure.
//
// Plan A: Recursion. Operation is embedded and may be specified with a callback (delegate).
// Plan B: Iteration with nested iterators. Little code, but large runtime footprint.
// Plan C: Iteration with 'manual' stack. More code, but efficient at runtime.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;

namespace DirTraversal01
{
    class Program
    {
        static string TestDir = @"C:\TESTDIR";

        static void Main (string[] args)
        {
            Console.WriteLine ("Plan A: Traverse directories with recursion:");
            PlanA (TestDir);

            Console.WriteLine ("Plan B: Traverse directories with nested iterators:");
            foreach (string dirName in TraversePlanB (TestDir))
                Console.WriteLine (dirName);

            Console.WriteLine ("Plan C: Traverse directories with encapsulated iterator:");
            foreach (string dirName in new TraversePlanC (TestDir))
                Console.WriteLine (dirName);
        }

        // Plan A: Old-style recursion. Consumer cannot use foreach semantics.
        static public void PlanA (string dirName)
        {
            Console.WriteLine (dirName);
            var subdirs = Directory.GetDirectories (dirName);
            foreach (var dir0 in subdirs)
                PlanA (dir0);
        }

        // Plan B: An elegant solution yet is less efficient due to nested iterators.
        static public IEnumerable<string> TraversePlanB (string dirName)
        {
            yield return dirName;

            foreach (string subdirName in Directory.GetDirectories (dirName))
                foreach (string yieldedSubdirName in TraversePlanB (subdirName))
                    yield return yieldedSubdirName;
        }
    }


    // Plan C: More code yet very efficient.
    public class TraversePlanC : IEnumerable<string>
    {
        private string[] Dirs;
        private int Counter;

        public TraversePlanC (string root)
        { this.Dirs = new string[] { root }; }

        private TraversePlanC (string[] dirs)
        { this.Dirs = dirs; }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        { return GetEnumerator(); }

        public IEnumerator<string> GetEnumerator()
        {
            var stack = new Stack<TraversePlanC>();

            for (var node = this;;)
            {
                yield return node.Dirs[node.Counter];

                string[] dirs = Directory.GetDirectories (node.Dirs[node.Counter]);
                if (dirs.Length > 0)
                {
                    stack.Push (node);
                    node = new TraversePlanC (dirs);
                    continue;
                }

                for (;;)
                {
                    if (++node.Counter < node.Dirs.Length)
                        break;
                    if (stack.Count == 0)
                        yield break;
                    node = stack.Pop();
                }
            }
        }
    }
}
